"""
限速的场景
##########

某一被保护的资源，只允许每个用户以特定的频率访问。

在该场景中出现的3种对象

资源: 可以是HTTP接口
用户: 可以是指定IP/User
频率：5/s 50/2m


限速的实现
##########

用户访问资源的时候，对其在当前访问窗口期的访问次数汇总，超过指定速率即可
认为过于频繁。

以redis为例:

根据指定频率算出当前的窗口期, 得到起始时间和结束时间
将资源+窗口期+用户标记拼接成字符串，以其为key, 过长则可以取hash结果
如果key不存在，将其初始化为1，并将该key的过期时间设为结束时间
如果存在，值加1；
如果该窗口期内访问次数大于频率规定的总次数，认为其过去频繁
"""
import hashlib
import re
import time
import zlib

# Extend the expiration time by a few seconds to avoid misses.
EXPIRATION_FUDGE = 5
_PERIODS = {
    's': 1,
    'm': 60,
    'h': 60 * 60,
    'd': 24 * 60 * 60,
}

_rate_re = re.compile(r'(\d+)/(\d*)([smhd])')


def split_rate(rate):
    if isinstance(rate, tuple):
        return rate
    match = _rate_re.match(rate)
    assert match, r'invalid {rate=}'
    count, multi, period = match.groups()
    count = int(count)
    seconds = _PERIODS[period.lower()]
    if multi:
        seconds = seconds * int(multi)
    return count, seconds


def _get_window(value, period):
    ts = int(time.time())
    if period == 1:
        return ts
    if not isinstance(value, bytes):
        value = str(value).encode('utf-8')
    w = ts - (ts % period) + (zlib.crc32(value) % period)
    if w < ts:
        return w + period
    return w


async def is_limited(visitor_tag,
                     rate,
                     limited_resource=None,
                     limited_group=None,
                     cache=None):
    """一个请求是否被限速
    :params limited_resource: 限速方法保护的资源
    :params visitor_tag: 访问者的标识
    :params rate: 限制的速率
    :params limited_group: 有可能是一组资源需要在一起进行限速保护
    """
    count, period = split_rate(rate)
    window = _get_window(visitor_tag, period)
    limited_resource = limited_resource or limited_group
    assert limited_resource is not None
    # 拼出当前用户/资源/时间窗口的唯一标识
    key = f'{limited_resource}-{visitor_tag}-{window}'
    key = hashlib.new('md5', key.encode('utf8')).hexdigest()
    assert cache is not None
    value = await cache.increase_or_init(key, period)
    if value > count:
        return True
    return False
